using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq.Expressions;

namespace Polymod.Tests
{
    [TestClass]
    public class AffectedPropertiesVisitorTests
    {
        public class ClassA
        {
            public int V1 { get; set; }
            public int V2 { get; set; }
            public ClassB VClassB { get; set; }
            public ClassB[] VClassBArray { get; set; }
        }

        public class ClassB
        {
            public int VB1 { get; set; }
            public int VB2 { get; set; }
            public ClassB VBRecursive { get; set; }
        }

        [TestMethod]
        public void SimpleTest()
        {
            var exp = Create(o => o.V1 + o.V2);
            AssertAffected(exp, "V1", "V2");
        }

        [TestMethod]
        public void SimpleCompisitionTest()
        {
            var exp = Create(o => o.V1 + o.VClassB.VB1);
            AssertAffected(exp, "V1", "VClassB", "VClassB.VB1");
        }

        [TestMethod]
        public void SimpleEnumerableCompisitionTest()
        {
            var exp = Create(o => o.VClassBArray.Sum(i => i.VB1));
            AssertAffected(exp, "VClassBArray", "VClassBArray.VB1");
        }

        [TestMethod]
        public void SimpleEnumerableCompisitionTest2()
        {
            var exp = Create(o => o.VClassBArray.Sum(i => i.VB1 * o.V2));
            AssertAffected(exp, "VClassBArray", "VClassBArray.VB1", "V2");
        }

        [TestMethod]
        public void MultipleMemberAccessTest()
        {
            var exp = Create(o => o.VClassB.VBRecursive.VBRecursive.VB1 * 8);
            AssertAffected(exp, "VClassB", "VClassB.VBRecursive", "VClassB.VBRecursive.VBRecursive", "VClassB.VBRecursive.VBRecursive.VB1");
        }

        [TestMethod]
        public void Enumerable_MultipleMemberAccessTest()
        {
            var exp = Create(o => o.VClassBArray.Sum(i => i.VB1 + i.VBRecursive.VB1 + o.V1));
            AssertAffected(exp, "VClassBArray", "VClassBArray.VB1", "VClassBArray.VBRecursive", "VClassBArray.VBRecursive.VB1", "V1");
        }

        [TestMethod]
        public void TwoEnumerable_MemberAccessTest()
        {
            var exp = Create(o => o.VClassBArray.Sum(i => i.VB1) + o.VClassBArray.Sum(i => i.VB2));
            AssertAffected(exp, "VClassBArray", "VClassBArray.VB1", "VClassBArray.VB2");
        }

        [TestMethod]
        public void SimpleEnumerableCompisitionTest2_Reordered()
        {
            var exp = Create(o => o.VClassBArray.Sum(i => o.V2 * i.VB1));
            AssertAffected(exp, "VClassBArray", "V2", "VClassBArray.VB1");
        }

        [TestMethod]
        public void SimpleEnumerableCompisitionTest3()
        {
            var exp = Create(o => o.VClassBArray.Sum(i => o.V2 * i.VB1 * o.V2 * i.VB2));
            AssertAffected(exp, "VClassBArray", "V2", "VClassBArray.VB1", "VClassBArray.VB2");
        }

        [TestMethod]
        public void SimpleEnumerableIndexerAccessTest()
        {
            var exp = Create(o => o.VClassBArray[1].VB1 * o.VClassBArray[2].VB2);
            AssertAffected(exp, "VClassBArray", "VClassBArray.VB1", "VClassBArray.VB2");
        }

        [TestMethod]
        public void SimpleFormulaTest()
        {
            var f1 = new Formula<ClassA, int>("F1", a => a.V1 * 2);
            var exp = Create(o => f1.Evaluate() - 18);
            AssertAffected(exp, "F1");
        }

        [TestMethod]
        public void SimpleEnumerableFormulaTest()
        {
            var f1 = new Formula<ClassB, int>("F1", b => b.VB2 * b.VB1);
            var exp = Create(o => o.VClassBArray.Sum(b => f1.Evaluate() - 18));
            AssertAffected(exp, "VClassBArray", "VClassBArray.F1");
        }

        [TestMethod]
        public void InheritedFormulaTest()
        {
            var f1 = new Formula<ClassB, int>("F1", b => b.VB2 * b.VB1);
            var f2 = new Formula<ClassB, int>("F2", b => f1.Evaluate() * 3);

            Expression<Func<ClassB, int>> exp = b => f2.Evaluate() * b.VB1;
            //Although F2 implies F1, only F2 and VB1 should be returned here...
            AssertAffected(exp, "F2", "VB1");
        }


        private void AssertAffected(Expression expression, params string[] expected)
        {
            var visitor = new AffectedPropertiesVisitor();
            visitor.Visit(expression);

            CollectionAssert.AreEquivalent(expected, visitor.AffectedProperties);
        }

        private Expression<Func<ClassA, R>> Create<R>(Expression<Func<ClassA, R>> expression)
        {
            return expression;
        }

    }
}
